﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Design;

namespace MethodWorx.Core.DomainModel
{
    using Extensions;

    public abstract class UnitOfWorkFactory : IUnitOfWorkFactory, IDisposable
    {
        private Stack<IUnitOfWork> stack = new Stack<IUnitOfWork>();

        #region IUnitOfWorkFactory Members

        public virtual IUnitOfWork Begin()
        {
            this.RemoveCompleteUnitOfWorkFromStack();

            if (this.stack.Count == 0)
            {
                //
                //  creating the inner unit of work
                //  that will be the root, e.g a LinqUnitOfWork, or whatever
                IUnitOfWork current = this.CreateRootUnitOfWork();
                if (current == null)
                    throw new NullReferenceException("CreateUnitOfWork");
                //  push 
                this.stack.Push(current);
            }
            else
            {
                IUnitOfWork current = this.CreateInnerUnitOfWork(this.stack.Peek());
                this.stack.Push(current);
            }

            return this.stack.Peek();
        }

        private void RemoveCompleteUnitOfWorkFromStack()
        {
            //
            //  ok, go through the stack and pop the close
            //  units of work of the stack
            while (this.stack.Count > 0)
            {
                if (!this.stack.Peek().IsOpen)
                    this.stack.Pop();
                else
                    break;
            }

        }

        protected abstract IUnitOfWork CreateRootUnitOfWork();

        protected virtual IUnitOfWork CreateInnerUnitOfWork(IUnitOfWork outerUnitOfWork)
        {
            //  create a managed inner unit of work that is responsible for collecting 
            //  changes to the objects and propagating changes to the outer unit of work
            return new InnerUnitOfWork(outerUnitOfWork);
        }

        public void Dispose()
        {
            //  remove the complete unit of works from the stack
            this.RemoveCompleteUnitOfWorkFromStack();

            while (this.stack.Count > 0)
            {
                //  pop it off and cancel
                IUnitOfWork unitOfWork = this.stack.Pop();
                if (unitOfWork.IsOpen)
                {
                    unitOfWork.Cancel();
                }
            }
        }

        #endregion
    }
}
